/*
 * TIDA.cpp
 *
 *  Created on: 07.11.2011
 *      Author: Ignacio Delgado-Martinez, MD-PhD
 *      Department of Neurosurgery - Charité (Berlin, Germany)
 *      Departmet of ..... - Max-Delbrueck-Center (Berlin, Germany)
 */

#include "TIDA.h"
#include "error.h"
using neurignacio::error;

// iostream is included in header
using std::cout;
using std::endl;
using std::ios;
using std::ifstream;
using std::ofstream;
using std::ostringstream;
#include <boost/filesystem.hpp>
#	ifndef BOOST_FILESYSTEM
#		define BOOST_FILESYSTEM 3
#	endif
#	if BOOST_FILESYSTEM>=2
#		define BOOST_TO_STRING(s) s.string()
#		define native() native_file_string()
#		define leaf() filename()
# 	endif

using boost::filesystem::path;
using boost::filesystem::is_regular_file;
#include <boost/algorithm/string.hpp>
using boost::algorithm::to_lower;

#include <sstream>
#include <algorithm>
#include <iterator>

#include <fstream>
// Use READ_CLASS only on simple class object with structure padding
#define READ_CLASS(filestream, offset) (filestream).seekg( (offset), std::ios::beg);(filestream).read(reinterpret_cast<char* >(&(*this)), sizeof(*this));


namespace TIDA{

inline Header::Header(const Header& h)
	: fileVersion(h.fileVersion)
{
	// TIDA
	for (unsigned short int i=0; i<TIDA_BYTES; ++i)
		TIDA[i]=h.TIDA[i];

	// reserved
	for (unsigned short int i=0; i<RESERVED_BYTES; ++i)
			reserved[i]=h.reserved[i];
}

inline Header::Header(iFilestream_t& f, const offset_t& offset)
{
	READ_CLASS(f, offset);
}

} // end namespace TIDA for Header

namespace TIDA{

inline FileSegment::FileSegment(iFilestream_t &f, const offset_t& offset)
{
	f.seekg( offset, std::ios::beg);
	f.read(reinterpret_cast<char* >(&(*this).segmentType), SEGMENT_TYPE_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).versionNumber), VERSION_NUMBER_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).segmentName), SEGMENT_NAME_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).dataSize), DATA_SIZE_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).dataPointer), DATA_POINTER_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).reserved), RESERVED_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).numberOfChildren), NUMBER_OF_CHILDREN_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).childDirPointer), CHILD_DIR_POINTER_BYTES);
}

inline FileSegment::FileSegment(const FileSegment& s)
	: versionNumber(s.versionNumber), dataSize(s.dataSize), dataPointer(s.dataPointer), numberOfChildren(s.numberOfChildren), childDirPointer(s.childDirPointer)
{
	// segmentType array
	for (unsigned short int i=0; i<SEGMENT_TYPE_BYTES; ++i)
		segmentType[i]=s.segmentType[i];

	// segmentName
	for (unsigned short int i=0; i<SEGMENT_NAME_BYTES; ++i)
		segmentName[i]=s.segmentName[i];

	// reserved
		for (unsigned short int i=0; i<RESERVED_BYTES; ++i)
			reserved[i]=s.reserved[i];
}

Children_List_t FileSegment::getChildrenList(iFilestream_t &f) const
{
	Children_List_t childrenList;
	for (numberOfChildren_t childIndex=0; childIndex<numberOfChildren; ++childIndex)
	{
		offset_t offset=getChildDirPointer() + ChildDirectory::TOTAL_BYTES*childIndex;
		ChildDirectory child(f, offset);
//		cout << "\tOPERATOR<< in line " << __LINE__ << endl;
//		cout << "\t" << child << endl;
		childrenList.push_back(child);
	}
	return childrenList;
}


ChildDirectory::ChildDirectory(const ChildDirectory &d)
: pointerToFileSegment(d.pointerToFileSegment)
{
	// nameOfFileSegment array
	for (unsigned short int i=0; i<NAME_OF_FILE_SEGMENT_BYTES; ++i)
		nameOfFileSegment[i]=d.nameOfFileSegment[i];
}

ChildDirectory::ChildDirectory(iFilestream_t &f, const offset_t& offset)
{
//	READ_CLASS(f, offset);
	f.seekg( offset, std::ios::beg);
	f.read(reinterpret_cast<char* >(&(*this).nameOfFileSegment), NAME_OF_FILE_SEGMENT_BYTES);
	f.read(reinterpret_cast<char* >(&(*this).pointerToFileSegment), POINTER_TO_FILE_SEGMENT_BYTES);
//	cout << nameOfFileSegment << " " << pointerToFileSegment << endl;
}


Recpar::Recpar(iFilestream_t &f, const offset_t& offset)
	: FileSegment(f, offset)
{
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_DESCRIPTION()) && dataSize==DATA_SIZE_BYTES)
	{  // Segment is a Recpar segment
		f.seekg( dataPointer, std::ios::beg);
		f.read(reinterpret_cast<char* >(&(*this).reserved), RESERVED_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).numberOfBlocks), NUMBER_OF_BLOCKS_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).numberOfChannels), NUMBER_OF_CHANNELS_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).numberOfPointsPerBlock), NUMBER_OF_POINTS_PER_BLOCK_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).frequency), FREQUENCY_BYTES);
//		cout << dataPointer << endl;
//		cout << numberOfBlocks << endl;
//		cout << numberOfChannels << endl;
//		cout << numberOfPointsPerBlock << endl;
//		cout << frequency << endl;

	}
	else
		error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());
}

Scalpar::Scalpar(iFilestream_t &f, const offset_t& offset)
	: FileSegment(f, offset)
{
//	cout << "Scalpar Segment Name: " << getSegmentName()  << " Segment Type: " << getSegmentType() << " dataSize: " << dataSize << endl;
//	cout << "SEGMENT_DESCRIPTION(): " << SEGMENT_DESCRIPTION() << " DATA_SIZE: " << DATA_SIZE_BYTES << endl;
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_DESCRIPTION()) && (dataSize % DATA_SIZE_BYTES) == 0) // Each channel is DATA_SIZE_BYTES, therefore dataSize = nChannels * DATA_SIZE_BYTES
	{  // Segment is a Scalpar segment
		f.seekg(dataPointer, std::ios::beg);
		f.read(reinterpret_cast<char* >(&(*this).typeOfData), TYPE_OF_DATA_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).titleOfXAxis), TITLE_OF_X_AXIS_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).reserved_X), RESERVED_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).xFactor), X_FACTOR_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).xOffset), X_OFFSET_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).titleOfYAxis), TITLE_OF_Y_AXIS_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).reserved_Y), RESERVED_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).yFactor), Y_FACTOR_BYTES);
		f.read(reinterpret_cast<char* >(&(*this).yOffset), Y_OFFSET_BYTES);
	}
	else
		error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());
}

Comments::Comments(iFilestream_t &f, const offset_t& offset)
	: FileSegment(f, offset)
{
	//cout << "COMMENTS Data Pointer: " << dataPointer << " Data Size: " << dataSize << endl;
//	cout << "COMMENTS getSegmentName: "<<getSegmentName();
//	cout << " SEGMENT_DESCRIPTION: " << SEGMENT_DESCRIPTION();
//	cout << " getSegmentType: " << getSegmentType();
//	cout << " dataSize: " << dataSize;
//	cout << " DATA_SIZE_BYTES: " << DATA_SIZE_BYTES << endl;
	char buffer[dataSize];
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_DESCRIPTION()) )// && dataSize<=DATA_SIZE_BYTES)
	{  // Segment is a Comments segment
		f.seekg( dataPointer, std::ios::beg);
		//f.read(reinterpret_cast<char* >(&(*this).Comment_v1), dataSize);
		f.read(reinterpret_cast<char* >(&buffer), dataSize);
	}
	else
		error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());

	if (isVersion2())
	{
		// Splits the String
		ListOfComments_t tempList;
		for (int i=0; i<dataSize; ++i)
		{
			string tempStr;
			while (buffer[i]!='\0' && i<dataSize)
			{
				tempStr.push_back(buffer[i]); // for char to char* conversion I need '&'
				++i;
			}
			tempList.push_back(tempStr);
		}
		// Store Comments
		calFile=tempList[CAL_FILE_POS];
		stmFile=tempList[STM_FILE_POS];
		timeOfRecording=tempList[TIME_OF_RECORDING_POS];
		stopWatch=tempList[STOP_WATCH_POS];
		additionalComment=tempList[ADDITIONAL_COMMENT_POS];
		for (int i=1; i<=MAX_COMMENTS; ++i)
		{
			defaultComment.push_back(tempList[STOP_WATCH_POS+i]);
			titleComment.push_back(tempList[ADDITIONAL_COMMENT_POS+i]);
		}
	}
	else	// is Version 1
	{
		for (int i=0; i<(dataSize<DATA_SIZE_BYTES? dataSize: DATA_SIZE_BYTES); ++i) // Comment_v1 size is equal to DATA_SIZE_BYTES, there limit check
		{
			Comment_v1[i]=buffer[i];
		}
	}
}


RawData::RawData(iFilestream_t &f, const offset_t& offset, const Recpar &recpar, const Scalpar &scalpar)
	: FileSegment(f, offset)
{
//	cout << "Raw Data (data size): " << dataSize << endl;
//	cout << "Segment Name: " << getSegmentName() << endl;
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_DESCRIPTION()))
	{  // Segment is a RAW_DATA segment
		if (scalpar.isTypeINTR() || scalpar.isTypeDATA())
		{
			SmallInt tempArray[recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock()];
			f.seekg( dataPointer, std::ios::beg);
			f.read(reinterpret_cast<char* >(&tempArray), dataSize);
			intVec3_t tempData(recpar.getNumberOfChannels(),
								intVec2_t(recpar.getNumberOfBlocks(),
								intVec1_t(recpar.getNumberOfPointsPerBlock()) ) ); // Creates a temp array for data
//			cout << "Total=" << recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock() << endl;
			for (long int i=0; i<recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock(); ++i)
			{

				long int channel=i % recpar.getNumberOfChannels();
				long int point=(i / recpar.getNumberOfChannels() ) % recpar.getNumberOfPointsPerBlock();
				long int block=(i / (recpar.getNumberOfChannels() * recpar.getNumberOfPointsPerBlock()) );
//				cout << i << " " << point << " " << block << " " << channel << endl;
				tempData.at(channel).at(block).at(point)=tempArray[i];
			}
			intData=tempData;
			floatData.clear();
		}
		else if (scalpar.isTypeREAL())
		// STILL NOT TESTED!!!!!!
		{
			SingleFloat tempArray[recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock()];
			f.seekg( dataPointer, std::ios::beg);
			f.read(reinterpret_cast<char* >(&tempArray), dataSize);
			floatVec3_t tempData(recpar.getNumberOfChannels(),
								floatVec2_t(recpar.getNumberOfBlocks(),
								floatVec1_t(recpar.getNumberOfPointsPerBlock()) ) ); // Creates a temp array for data
//			cout << "Total=" << recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock() << endl;
			for (long int i=0; i<recpar.getNumberOfBlocks()*recpar.getNumberOfChannels()*recpar.getNumberOfPointsPerBlock(); ++i)
			{

				long int channel=i % recpar.getNumberOfChannels();
				long int point=(i / recpar.getNumberOfChannels() ) % recpar.getNumberOfPointsPerBlock();
				long int block=(i / (recpar.getNumberOfChannels() * recpar.getNumberOfPointsPerBlock()) );
//				cout << i << " " << point << " " << block << " " << channel << endl;
				tempData.at(channel).at(block).at(point)=tempArray[i];
			}
			floatData=tempData;
			intData.clear();

		}
		else
			error(neurignacio::TYPE_OF_DATA_UNKNOWN, SEGMENT_DESCRIPTION()+"*"+scalpar.getTypeOfData());
	}
		else
			error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());


//	cout << "last point=" << intData[0][0][1814999] << endl;
}

StmData::StmData(iFilestream_t &f, const offset_t& offset)
	: FileSegment(f, offset)
{
	char tempStr[dataSize];
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_DESCRIPTION()))
	{  // Segment is a STM_DTA segment
		f.seekg(dataPointer, std::ios::beg);
		f.read(reinterpret_cast<char* >(&(tempStr)), dataSize);
	}
	else
		error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());
	commands=string(tempStr, dataSize);
}

AmplPar2::AmplPar2(iFilestream_t &f, const offset_t& offset)
: FileSegment(f,offset)
{
//	cout << "AMPLPAR2 Datapointer: " << dataPointer << endl;
	if (!getSegmentName().compare(SEGMENT_DESCRIPTION()) && !getSegmentType().compare(SEGMENT_TYPE()) && (dataSize % AMPLPAR_BASE_SIZE==0))
	{  // Segment is a AmplPar2 segment
		numberOfAmplifier_t nAmplifiers=dataSize / AMPLPAR_BASE_SIZE;
		f.seekg(dataPointer, std::ios::beg);
		for (numberOfAmplifier_t amplifierIndex=0; amplifierIndex<nAmplifiers; ++amplifierIndex)
		{
			AmplPar2_Base amplifier;
			f.read(reinterpret_cast<char* >(&amplifier.Range), amplifier.RANGE_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Gain),amplifier.GAIN_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Vmem),amplifier.VMEM_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.LJ),amplifier.LJ_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.V0),amplifier.V0_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.CFastSum),amplifier.CFASTSUM_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.CFastPercent),amplifier.CFASTPERCENT_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.CFastTau),amplifier.CFASTTAU_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Cslow),amplifier.CSLOW_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Rseries),amplifier.RSERIES_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.RsComp),amplifier.RSCOMP_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.RsCompTau),amplifier.RSCOMPTAU_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.LeakComp),amplifier.LEAKCOMP_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Filter1Index),amplifier.FILTER1INDEX_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Filter2Bessel),amplifier.FILTER2BESSEL_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Filter2Freq),amplifier.FILTER2FREQ_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Stim),amplifier.STIM_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.ExtStimScal),amplifier.EXTSTIMSCAL_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.BoardNumber),amplifier.BOARDNUMBER_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.OrigVersion),amplifier.ORIGVERSION_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.NewParasValid),amplifier.NEWPARASVALID_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.MuxSource),amplifier.MUXSOURCE_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.CSlowRange),amplifier.CSLOWRANGE_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Mode),amplifier.MODE_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Imon),amplifier.IMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.TitleIMon),amplifier.TITLEIMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.UnitIMon),amplifier.UNITIMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.Rmon),amplifier.RMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.TitleVMem),amplifier.TITLEVMEM_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.UnitVmem),amplifier.UNITVMEM_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.TitleRMon),amplifier.TITLERMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.UnitRMon),amplifier.UNITRMON_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.MPCheck),amplifier.MPCHECK_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.NoiseCheck),amplifier.NOISECHECK_BYTES);
			f.read(reinterpret_cast<char*>(&amplifier.CalcRCheck),amplifier.CALCRCHECK_BYTES);

			parameters.push_back(amplifier);
		}
	}
		else
			error(neurignacio::SEGMENT_MISMATCH, SEGMENT_DESCRIPTION());
//	cout << "SIZE OF AMPLPAR2 " << parameters.size() << endl;
}


// *************** printing operations ***********************************
// 	Header
ostream& operator<<(ostream& out, const TIDA::Header& h)
{
	out << h.getTIDA() << " ";
	out << "File Version: " << h.getFileVersion() << "";
	return out;
}

void Header::info(void) const
{
	cout << (*this) << endl;
}
//	Child Directory
ostream& operator<<(ostream& out, const TIDA::ChildDirectory& d)
{
	out << "" << d.getNameOfFileSegment() << "";
	out << "->&0x" << std::hex << d.getPointerToFileSegment() << "";
	out << std::dec;
	return out;
}
// 	File Segment
ostream& operator<<(ostream& out, const TIDA::FileSegment& s)
{
	out << "" << s.getSegmentName();
	out << " (Type=" << s.getSegmentType() << ", ";
	out << "version=" << s.getVersionNumber() << ", ";
	out << "Data Size=" << s.getDataSize() << "";
	out << std::dec << "->&"<< std::dec << s.getDataPointer() << ") ";
	out << "{Children=" << s.getNumberOfChildren() << "";
	out << "->&0x" << std::dec << s.getChildDirPointer() << "}";
	out << std::dec;
	return out;
}
// 	Recpar
ostream& operator<<(ostream& out, const TIDA::Recpar& recpar)
{
	out << static_cast<FileSegment>(recpar)<< " ";
	out << "[Blocks=" << recpar.getNumberOfBlocks() << ", ";
	out << "Channels=" << recpar.getNumberOfChannels() << ", ";
	out << "Points per Block=" << recpar.getNumberOfPointsPerBlock() << ", ";
	out << "Frequency=" << recpar.getFrequency() << "]";

	return out;
}
void Recpar::info(void) const
{
	cout << "Recording Parameters: " << endl;
	cout << "\tChannels: " << getNumberOfChannels() << endl;
	cout << "\tBlocks: " << getNumberOfBlocks() << endl;
	cout << "\tPoints per Block: " << getNumberOfPointsPerBlock() << endl;
	
}
// Scalpar
ostream& operator<<(ostream& out, const TIDA::Scalpar& scalpar)
{
	out << static_cast<FileSegment>(scalpar)<< " ";
	out << "[Type=" << scalpar.getTypeOfData()<<" ";
	out << "<X-Axis=" << (scalpar.getTitleOfXAxis().empty()? "EMPTY": scalpar.getTitleOfXAxis())<< ", ";
	out << "X-Factor=" << scalpar.getXFactor() << ", ";
	out << "X-Offset=" << scalpar.getXOffset() << "> ";
	out << "<Y-Axis=" << (scalpar.getTitleOfYAxis().empty()? "EMPTY": scalpar.getTitleOfYAxis())<< ", ";
	out << "Y-Factor=" << scalpar.getYFactor() << ", ";
	out << "Y-Offset=" << scalpar.getYOffset() << "> ";
	out<<"]" ;
	return out;
}
void Scalpar::info(void) const
{
	cout << "Scaling Parameters: " << endl;
	cout << "\tX-Axis Label: " << getTitleOfXAxis() << endl;
	cout << "\tX-Factor: " << getXFactor() << endl;
	cout << "\tX-Offset: " << getXOffset() << endl;
	cout << "\tY-Axis Label: " << getTitleOfYAxis() << endl;
	cout << "\tY-Factor: " << getYFactor() << endl;
	cout << "\tY-Offset: " << getYOffset() << endl;
}
// Comments
ostream& operator<<(ostream& out, const TIDA::Comments& comments)
{
	out << static_cast<FileSegment>(comments)<< " ";
	if (comments.isVersion1())
		out << "[" << comments.getComment_v1() << "]";
	else if (comments.isVersion2())
	{
		out <<"[";
		out << "CAL=" << (comments.getCalFile().empty()? "EMPTY": comments.getCalFile()) << ", ";
		out << "STM=" << (comments.getStmFile().empty()? "EMPTY" : comments.getStmFile()) << ", ";
		out << "Time=" <<  (comments.getTimeOfRecording().empty()? "EMPTY" : comments.getTimeOfRecording())<< ", ";

		bool emptyComments=true;
		out << "< Default Comments=";
		if (comments.getDefaultComments().empty())
			out << "EMPTY";
		for (unsigned char i=0; i<comments.getDefaultComments().size(); ++i)
		{
			if (!comments.getDefaultComments()[i].empty())
			{
				emptyComments=false;
				out << "#" << i <<"=\"" << comments.getDefaultComments()[i]<<"\"";
				if (i+1!=comments.getDefaultComments().size()) // If it's not the last, append comma
					out << ",";
				out << " ";
			}

		}
		if (emptyComments)
			out << "EMPTY";
		out << " > ";

		string tempStr=comments.getAdditionalComment().c_str();
		boost::replace_all(tempStr, "\n", "\\n");
		boost::replace_all(tempStr, "\r", "\\r");
		out << "< Additional Comments=\"" << (comments.getAdditionalComment().empty()? "EMPTY": tempStr) << "\"> ";

		emptyComments=true;
		out << "< Title Comments=";
		if (comments.getTitleComments().empty())
			out << "EMPTY";
		for (unsigned char i=0; i<comments.getTitleComments().size(); ++i)
		{
			if (!comments.getTitleComments()[i].empty())
			{
				emptyComments=false;
				out << "#" << i <<"=\"" << comments.getTitleComments()[i]<<"\"";
				if (i+1!=comments.getTitleComments().size()) // If it's not the last, append comma
					out << ",";
				out << " ";
			}

		}
		if (emptyComments)
			out << "EMPTY";
		out << " >";
		out <<"]";
	}
	else
		out << "[" << "VERSION NOT SUPPORTED" << "]";
	return out;
}

void Comments::info(void) const
{
	cout << "Comments:" << endl;
	if (isVersion1())
		cout << "\t[" << getComment_v1() << "]";
	if (isVersion2())
	{
		if (!getCalFile().empty())
			cout << "\tCalibration File: " << getCalFile() << endl;
		if (!getStmFile().empty())
			cout << "\tStimulation File: " << getStmFile() << endl;
		if (!getTimeOfRecording().empty())
			cout << "\tAdquisition Time: " <<  getTimeOfRecording()<< endl;

		if (!getDefaultComments().empty())
		{
			bool emptyComments=true;
			ostringstream out;
			for (unsigned char i=0; i<getDefaultComments().size(); ++i)
			{
				if (!getDefaultComments()[i].empty())
				{
					emptyComments=false;
					out << "\t\t#" << i <<": " << getDefaultComments()[i];
					if (i+1!=getDefaultComments().size()) // If it's not the last, append newline
						out << endl;
					//out << " ";
				}

			}
			if (!emptyComments)
			{
				cout << "\tDefault Comments: " << endl;
				cout << out << endl;
			}
		}

		if (!getAdditionalComment().empty())
		{
			string tempStr=getAdditionalComment().c_str();
			boost::replace_all(tempStr, "\r\n", "\r\n\t\t"); // Add two tabulations
			cout << "\tAdditional Comments: " << endl;
			cout << "\t\t"<< tempStr << endl;
		}
		
		if (!getTitleComments().empty())
		{
			bool emptyComments=true;
			ostringstream out;
			for (unsigned char i=0; i<getTitleComments().size(); ++i)
			{
				if (!getTitleComments()[i].empty())
				{
					emptyComments=false;
					out << "\t\t#" << i <<": " << getTitleComments()[i];
					if (i+1!=getTitleComments().size()) // If it's not the last, append newline
						out << endl;
//					out << " ";
				}

			}
			if (!emptyComments)
			{
				cout << "\tTitleComments: " << endl;
				cout << out << endl;
			}
		}
	}
}
// 	Rawdata
ostream& operator<<(ostream& out, const TIDA::RawData& rawdata)
{
	out << static_cast<FileSegment>(rawdata)<< " ";
	out << "[";
	if (rawdata.isIntData() && !rawdata.isFloatData())
		out << "integer";
	else if (rawdata.isFloatData() && !rawdata.isIntData())
		out << "float";
	else if (!rawdata.isFloatData() && !rawdata.isIntData())
		out << "EMPTY";
	else
		out << "UNKNOWN";
	out << "]";
	return out;
}
// StmData
ostream& operator<<(ostream& out, const StmData& stmData)
{
	out << static_cast<FileSegment>(stmData)<< " ";
	out << stmData.getCommands();
	return out;
}

void StmData::info() const
{
	cout << "Stimulation Commands:" << endl;
	cout << getCommands() << endl;
}

//	AmplPar2_Base
ostream& operator<<(ostream& out, const AmplPar2_Base& a)
{
	out << "\tRange=" << a.Range<< endl;
	out << "\tGain=" << a.Gain<< endl;
	out << "\tVmem=" << a.Vmem<< endl;
	out << "\tLJ=" << a.LJ<< endl;
	out << "\tV0=" << a.V0<< endl;
	out << "\tCFastSum=" << a.CFastSum<< endl;
	out << "\tCFastPercent=" << a.CFastPercent<< endl;
	out << "\tCFastTau=" << a.CFastTau<< endl;
	out << "\tCslow=" << a.Cslow<< endl;
	out << "\tRseries=" << a.Rseries<< endl;
	out << "\tRsComp=" << a.RsComp<< endl;
	out << "\tRsCompTau=" << a.RsCompTau<< endl;
	out << "\tLeakComp=" << a.LeakComp<< endl;
	out << "\tFilter1Index=" << a.Filter1Index<< endl;
	out << "\tFilter2Bessel=" << a.Filter2Bessel<< endl;
	out << "\tFilter2Freq=" << a.Filter2Freq<< endl;
	out << "\tStim=" << a.Stim<< endl;
	out << "\tExtStimScal=" << a.ExtStimScal<< endl;
	out << "\tBoardNumber=" << a.BoardNumber<< endl;
	out << "\tOrigVersion=" << a.OrigVersion<< endl;
	out << "\tNewParasValid=" << a.NewParasValid<< endl;
	out << "\tMuxSource=" << a.MuxSource<< endl;
	out << "\tCSlowRange=" << a.CSlowRange<< endl;
	out << "\tMode=" << a.Mode<< endl;
	out << "\tImon=" << a.Imon<< endl;
	out << "\tTitleIMon=" << a.TitleIMon<< endl;
	out << "\tUnitIMon=" << a.UnitIMon<< endl;
	out << "\tRmon=" << a.Rmon<< endl;
	out << "\tTitleVMem=" << a.TitleVMem<< endl;
	out << "\tUnitVmem=" << a.UnitVmem<< endl;
	out << "\tTitleRMon=" << a.TitleRMon<< endl;
	out << "\tUnitRMon=" << a.UnitRMon<< endl;
	out << "\tMPCheck=" << a.MPCheck<< endl;
	out << "\tNoiseCheck=" << a.NoiseCheck<< endl;
	out << "\tCalcRCheck=" << a.CalcRCheck<< endl;
	return out;
}

// AmplPar2
ostream& operator<<(ostream& out, const AmplPar2& amplpar2)
{
	out << "Number of Amplifiers: " << (int)amplpar2.getNumberOfAmplifiers() << endl;
	for (int amplifier=0; amplifier<amplpar2.getNumberOfAmplifiers(); ++amplifier)
	{
		out << "Amplifier " << amplifier+1 << endl;
		out << amplpar2.getAmplifierParameters(amplifier) << endl;
	}
	return out;
}

void AmplPar2::info(const int amplifier) const
{
	cout << "Amplifier Parameters:" << endl;
	if (amplifier==0)
		cout << (*this);
	else if (amplifier > getNumberOfAmplifiers())
		error(neurignacio::AMPLIFIER_NOT_FOUND, amplifier);
	else
	{
		cout << "Amplifier " << amplifier << endl;
		cout << (*this)[amplifier-1] << endl;
	}
}

//	TIDAFile

void TIDAFile::info(void) const
{
	cout << "File: " << filename << endl;
	header.info();
	recpar.info();
	scalpar.info();
	comments.info();
	amplpar2.info();
}

void TIDAFile::viewFileStructure(void) const
{
	cout << header << endl;
	cout << rootWholeFile << endl;
	cout << tiRoot << endl;
	cout << recpar << endl;
	cout << scalpar << endl;
	cout << comments << endl;
	cout << rawdata << endl;
	cout << static_cast<FileSegment>(amplpar2) << endl;
}



// *********** TIDAFile Constructor ******************************************
TIDAFile::TIDAFile(const string& file, const bool verbose)
	: filename(file)
{
	if (filename.empty())
		error(neurignacio::FILENAME_IS_EMPTY, filename);
	if (!is_regular_file(filename))
		error(neurignacio::IS_NOT_REGULAR_FILE, filename);
	// f contains a pointer to the original TIDA file
	iFilestream_t f(file.c_str(), ios::in | ios::binary);
	if (!f)
		error(neurignacio::CANNOT_OPEN_FILE, filename);
	if (verbose) cout << "Loading " << file << endl;
	// Calculate the size of the file
	f.seekg(0,ios::end);
	sizeOfFile=f.tellg();

	// Read File Header
	filePosition_t pos=0;
	header=Header(f,pos);

	if (!header.isTIDA()) // file is not TIDA
		error(neurignacio::FILE_FORMAT_ERROR, filename);
	pos += f.tellg(); // Go to current position (i.e. last read byte)
	// Read Root Whole File
	rootWholeFile=FileSegment(f,pos);

	// Start reading file
	// 1. Children of Root Whole File (GARBAGE and TI_ROOT)
	rootWholeFileChildren=rootWholeFile.getChildrenList(f);
	tiRoot=FileSegment(f, rootWholeFileChildren[1].getPointerToFileSegment());
	// 2. Children of TI_ROOT
	tiRootChildren=tiRoot.getChildrenList(f);
	if (tiRootChildren.size()!=NUMBER_OF_CHILDREN)
		error(neurignacio::CHILDREN_MISSMATCH,tiRootChildren.size());
	// 3. Read Parameters
	// recpar
	recpar=Recpar(f,tiRootChildren[RECPAR_POS].getPointerToFileSegment());
//	cout << "RECPAR:" << endl;
//	cout << recpar << endl;

	// scalpar
	scalpar=Scalpar(f, tiRootChildren[SCALPAR_POS].getPointerToFileSegment());
//	cout << "SCALPAR:" << endl;
//	cout << "scalpar" << endl;

	// Comments
	comments=Comments(f, tiRootChildren[COMMENTS_POS].getPointerToFileSegment());

	// Raw_data
	rawdata=RawData(f, tiRootChildren[RAWDATA_POS].getPointerToFileSegment(), recpar, scalpar);

	// Stm_data
	stmData=StmData(f, tiRootChildren[STMDTA_POS].getPointerToFileSegment());


	// AmplPar2
	amplpar2=AmplPar2(f, tiRootChildren[AMPLPAR2_POS].getPointerToFileSegment());

	// child 6
	FileSegment child6=FileSegment(f, tiRootChildren[6].getPointerToFileSegment());

	f.close();

}


// *********** TIDA to ASCII Member ******************************************

bool TIDAFile::toASCII(const string destinationFilename, const bool overwrite, const bool verbose, const string separator, const string newline)
// Default Destination Filename Format:
// filename_extension_channel.extension
// inside text file, first column is the X-Axis
// each of the following columns corresponds to the each of the blocks
{
	const string TIDA_ASCII_EXTENSION = ".tida.txt";
	const string NEWLINE=newline;
	const string SEPARATOR=separator;
	const bool OVERWRITE=overwrite;
	string destination=destinationFilename;
	string extension;
	if (destinationFilename.empty())
	{
		path file = filename;
		destination  = file.parent_path().native();
		if (!destination.empty()) destination += "/";
		destination += file.stem();
		destination += "_" + file.extension().substr(1);
		extension = TIDA_ASCII_EXTENSION;
	}
	else
	{
		path file = destinationFilename;
		destination  = file.parent_path().native();
		if (!destination.empty()) destination += "/";
		destination += file.stem();
		extension = file.extension();
	}

	int nChannels=recpar.getNumberOfChannels();
	int nBlocks = recpar.getNumberOfBlocks();
	int nPoints = recpar.getNumberOfPointsPerBlock();
	for (int channel=0; channel<nChannels; ++channel)
	{
		ostringstream destinationFilename;
		destinationFilename << destination << "_c" << channel+1 << extension;
		if (boost::filesystem::exists(destinationFilename.str()))
			if ((OVERWRITE==true) && (verbose))
				cout << "Warning: " << destinationFilename.str() << " already exists and is overwritten" << endl;
			if (OVERWRITE==false)
				error(neurignacio::FILE_EXISTS, destinationFilename.str());
		ofstream fileOut(destinationFilename.str().c_str());
		if (!fileOut)
			error(neurignacio::CANNOT_CREATE_FILE, destinationFilename.str());
		if (verbose) cout << "Writing " << destinationFilename.str().c_str() << " ... " << endl;
		for (int point=0; point<nPoints; ++point)
		{
			fileOut << getXScaled(point) << SEPARATOR;
			for (int block=0; block<nBlocks; ++block)
				fileOut << getYScaled(point, block, channel) << SEPARATOR;
			fileOut << NEWLINE;
		}
	}

	if (verbose) cout << "Done" << endl;
	return true;
}

} // endnamespace TIDA
